grammar Language;

tokens {
	CONST = 'const';
	GRAPH = 'graph';
	SWITCH = 'switch';
	DICTMAP = 'map';
	EMAP = 'emap';
	KMAP = 'kmap';
	NATIVE = 'native';
	FACTORY = 'factory';
	TRUE = 'true';
	FALSE = 'false';
	OPTION = 'option';
	CLASS = 'classref';
	VAR = 'var';
	VARS = 'vars';
	PARAMETERS = 'parameters';
	FACTORIES = 'factories';
	DICTS = 'dicts';
	SYNC = 'sync';
	NULL = 'null';
}

@header{
package ac.jp.u_tokyo.SyncLib.language;
import java.util.LinkedList;
import java.util.List;
}

@members {
Program _program;
}

@lexer::header{
package ac.jp.u_tokyo.SyncLib.language;
import java.io.File;
}

@lexer::members {
    class SaveStruct {
      SaveStruct(CharStream input){
        this.input = input;
        this.marker = input.mark();
      }
      public CharStream input;
      public int marker;
      public File basePath;
     }
     
     File basePath = new File(".");
     public void setBasePath(String path){
     	basePath = new File(path);
     }
     

     Stack<SaveStruct> includes = new Stack<SaveStruct>();

    // We should override this method for handling EOF of included file
     public Token nextToken(){
       Token token = super.nextToken();

       if(token==Token.EOF_TOKEN && !includes.empty()){
        // We've got EOF and have non empty stack.
         SaveStruct ss = includes.pop();
         basePath = ss.basePath;
         setCharStream(ss.input);
         input.rewind(ss.marker);
         token = super.nextToken();
       }

      // Skip first token after switching on another input.
      // You need to use this rather than super as there may be nested include files
       if(((CommonToken)token).getStartIndex() < 0)
         token = this.nextToken();

       return token;
     }
 }


fragment DIGIT 
	:	'0'..'9';
	
fragment ULETTER
	:	'A'..'Z';
fragment LLETTER
	:	'a'..'z';
fragment LETTER
	:	ULETTER | LLETTER | '_';	

NUMBER : DIGIT+;
NAME 	:	LETTER (DIGIT | LETTER)*;
STRING : ('\"' | '\'') (~('\"' | '\''))* ('\"' | '\'');


COMMENT			
		:	'/*'  ( options {greedy=false;}:.)* '*/' 			  {skip();}
		|	'/' '\\' '\n' '*'  ( options {greedy=false;}:.)* '*/' {skip();}
		;
LINE_COMMENT :	'//'(~('\n' | '\r')*)('\n' | '\r') {skip();};

WS
    : (' '| '\t' | '\n' | '\r')+ {skip();};
    
 INCLUDE
     : 'include' (WS)? f=STRING {
       String name = f.getText();
       name = name.substring(1,name.length()-1);
       try {
        // save current lexer's state
         SaveStruct ss = new SaveStruct(input);
         ss.basePath = basePath;
         includes.push(ss);
         
         name = basePath.getAbsolutePath() + File.separator + name;
         basePath = new File(name).getAbsoluteFile().getParentFile();

        // switch on new input stream
         setCharStream(new ANTLRFileStream(name));
         reset();

       } catch(Exception fnf) { throw new Error("Cannot open file " + name); }
     }
     ;
    
  
    
    
    
prog returns [Program result] 
	: {$result = new Program();} declarations[$result] EOF;
declarations[Program program] : (literal_declaration[$program] |  sync_declaration[$program])*;

literal_declaration[Program program] 
	: CONST NAME '=' primitive_value {$program.addConstDecl($NAME.text, $primitive_value.result);};
	
sync_declaration[Program program]
	: NAME {SyncDecl syncDecl = new SyncDecl($NAME.text);}
	(para_list[syncDecl])? '=' sync_body[$program] {syncDecl.setBody($sync_body.result); program.addSyncDecl($NAME.text, syncDecl);};
	
para_list[SyncDecl syncDecl] 
	: '<' para_decl[syncDecl]	(','  para_decl[syncDecl])*
	'>';
para_decl[SyncDecl syncDecl]
	: {Const c = null;} 
	NAME 
	('=' primitive_value {c = $primitive_value.result;})?
	{if (c == null) syncDecl.addNonDefaultPara($NAME.text); else syncDecl.addDefaultPara($NAME.text, c);};
	
sync_body[Program program] returns [SyncBody result] 
	: native_sync {$result = $native_sync.result;}
	| sync_ref[$program] {$result = $sync_ref.result;} 
	| graph[program] {$result = $graph.result;}
	| switch_combinator[program] {$result = $switch_combinator.result;}
	| dict[program] {$result = $dict.result;}
	| edict[program] {$result = $edict.result;};
	
switch_combinator[Program program] returns [SwitchBody result] 
	: SWITCH { $result = new SwitchBody($SWITCH.line);}
	'{' (sync_body[program] {$result.addInnerSync($sync_body.result);} ';')+ '}';

dict[Program program] returns [DictMapBody result] 
	: (DICTMAP { $result = new DictMapBody($DICTMAP.line); }
		| KMAP {$result = new KeyDictMapBody($KMAP.line);})
	'{'  
	SYNC '=' sync_body[program] ';'
	FACTORIES '=' '[' factory_list[$result] ']' ';' 
	'}' {$result.setInner($sync_body.result);};

edict[Program program] returns [EDictMapBody result] 
	: EMAP 
	'{'  
	SYNC '=' sync_body[program] ';'
	DICTS '=' primitive_value ';'
	'}' { $result = new EDictMapBody($EMAP.line, $sync_body.result, $primitive_value.result); };
	
factory_list[DictMapBody b]
	: v1=primitive_value {b.addFactory($v1.result);} 
	(',' v2=primitive_value {b.addFactory($v2.result);} )*;

	
graph[Program program] returns [GraphCombinatorBody result]
	: GRAPH {$result = new GraphCombinatorBody($GRAPH.line);} 
	'(' p=var_list ')'  {$result.addParameters($p.varList);}
	'{'
		((VAR | VARS) '=' v=var_list ';' {$result.addOtherVars($v.varList);})? 
		(sync_connection[program, result])* 
	'}';
	
var_list returns [List varList]
	: {$varList = new LinkedList();}
	n1=NAME {$varList.add(new GraphCombinatorBody.Var($n1.line, $n1.text));}
	(',' n2=NAME{$varList.add(new GraphCombinatorBody.Var($n2.line, $n2.text));})*;
	
sync_connection[Program program, GraphCombinatorBody g] 
	: sync_body[program] '(' var_list ')' ';' 
	{g.addConnection($sync_body.result, $var_list.varList);};
	
sync_ref[Program program] returns [SyncRef result]
	: NAME {$result = new SyncRef($NAME.line, $program, $NAME.text);}
	(arg_list[$result])?;

arg_list[SyncRef s] : '<' arg_decl[s] (',' arg_decl[s])* '>';
arg_decl[SyncRef s] : NAME '=' primitive_value { s.addArg($NAME.text, $primitive_value.result);};
	
native_sync returns [NativeSyncBody result]
	: NATIVE '{' 
		FACTORY '=' string  ';'
		PARAMETERS '=' NUMBER ';' { $result = new NativeSyncBody($NATIVE.line, $string.text, new Integer($NUMBER.text));} 
		(option[$result] ';')* 
	'}';

option[NativeSyncBody s] 
	: OPTION NAME '=' primitive_value { s.addOption($NAME.text, $primitive_value.result);};

primitive_value returns [Const result]
	: boolean_value {$result = $boolean_value.result;} 
	| string {$result = new StringConst($string.line, $string.text);}
	| NUMBER {$result = new IntegerConst($NUMBER.line, new Integer($NUMBER.text));}
	| obj_creation {$result = $obj_creation.result;} 
	| value_ref {$result = $value_ref.result;}
	| NULL {$result = new NullConst($NULL.line);}
	| '[' value_list  ']' {$result = $value_list.result;};

value_list returns [ArrayConst result]
	: 
	(p1=primitive_value {$result = new ArrayConst(p1.line);$result.addInner($p1.result);} 
	(',' p2=primitive_value {$result.addInner($p2.result);})*)?;

boolean_value returns [BooleanConst result]
	: TRUE {$result = new BooleanConst($TRUE.line, true);}
	| FALSE {$result = new BooleanConst($FALSE.line, false);};

obj_creation returns [ObjCreation result]
	: CLASS string {$result = new ObjCreation($string.line, $string.text);}; 
value_ref returns [ConstRef result]
	: NAME {$result = new ConstRef($NAME.line, $NAME.text);};

string returns [String text, int line]
	: STRING {$text = $STRING.text.substring(1, $STRING.text.length() - 1); $line = $STRING.line;};